package com.swensen.game;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JPanel;

import com.swensen.game.data.Sprite;
import com.swensen.game.data.Tile;
import com.swensen.game.data.TileMap;
import com.swensen.game.editor.data.Bounds;

/**
 * The Class Board.
 */
public abstract class Board extends JPanel implements Runnable, ComponentListener {
	
	/**
	 * The Class BoardKeyAdapter.
	 */
	private class BoardKeyAdapter extends KeyAdapter {
		
		/* (non-Javadoc)
		 * @see java.awt.event.KeyAdapter#keyPressed(java.awt.event.KeyEvent)
		 */
		@Override
		public void keyPressed(KeyEvent e) {
			boardKeyPressed(e);
			pressedKeys.add(e.getKeyCode());
		}
		
		/* (non-Javadoc)
		 * @see java.awt.event.KeyAdapter#keyReleased(java.awt.event.KeyEvent)
		 */
		@Override
		public void keyReleased(KeyEvent e) {
			boardKeyReleased(e);
			pressedKeys.remove(e.getKeyCode());
		}
	}
	
	/**
	 * The Interface FocusSprite.
	 */
	public interface FocusSprite extends Sprite {
		
	}
	
	/** The Constant animationLock. */
	protected static final Object animationLock = new Object();
	
	/** The Constant DELAY. */
	private static final int DELAY = 50;
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = -7080250889353081630L;
	/** The animator. */
	private ExecutorService animator;
	
	
	
	/** The board bounds. */
	protected Bounds boardBounds;
	
	/** The time the draw method starts. */
	protected long drawStartTime;
	
	/** The fsprite. */
	protected FocusSprite fsprite;
	
	/** The pressed keys. */
	protected SortedSet<Integer> pressedKeys = new TreeSet<Integer>();
	
	/** The tiles. */
	protected TileMap tiles;
	
	/** The view port. */
	protected Bounds viewPort;
	
	/**
	 * Instantiates a new board.
	 *
	 * @param fsprite the fsprite
	 */
	protected Board(FocusSprite fsprite) {
		this.fsprite = fsprite;
		addKeyListener(new BoardKeyAdapter());
		addComponentListener(this);
		setFocusable(true);
		setBackground(Color.BLACK);
		setDoubleBuffered(true);
		viewPort = new Bounds(0, 0, getWidth(), getHeight());
		newMap(25, 25);
	}
	
	/* (non-Javadoc)
	 * @see javax.swing.JComponent#addNotify()
	 */
	@Override
	public void addNotify() {
		super.addNotify();
		
		animator = Executors.newSingleThreadExecutor();
		animator.execute(this);
	}

	/**
	 * Board key pressed.
	 *
	 * @param e the e
	 */
	protected abstract void boardKeyPressed(KeyEvent e);

	/**
	 * Board key released.
	 *
	 * @param e the e
	 */
	protected abstract void boardKeyReleased(KeyEvent e);
	
	/**
	 * Board point to view port.
	 *
	 * @param x the x
	 * @param y the y
	 * @return the point
	 */
	protected Point boardPointToViewPort(int x, int y) {
		int xv = x - viewPort.getMinX();
		int yv = y - viewPort.getMinY();
		
		return new Point(xv, yv);
	}

	/**
	 * Calculate.
	 *
	 * @param vmax the vmax
	 * @param vmin the vmin
	 * @param wmax the wmax
	 * @param wmin the wmin
	 * @param p the p
	 * @return the int
	 */
	protected int calculate(double vmax, double vmin, double wmax, double wmin, double p) {
		return (int)((((vmax - vmin) / (wmax - wmin)) * (p - wmin)) + vmin);
	}
	
	/* (non-Javadoc)
	 * @see java.awt.event.ComponentListener#componentHidden(java.awt.event.ComponentEvent)
	 */
	@Override
	public void componentHidden(ComponentEvent e) {
		//No op
	}
	
	/* (non-Javadoc)
	 * @see java.awt.event.ComponentListener#componentMoved(java.awt.event.ComponentEvent)
	 */
	@Override
	public void componentMoved(ComponentEvent e) {
		//No op
	}
	
	/* (non-Javadoc)
	 * @see java.awt.event.ComponentListener#componentResized(java.awt.event.ComponentEvent)
	 */
	@Override
	public void componentResized(ComponentEvent e) {
		//No op
	}
	
	/* (non-Javadoc)
	 * @see java.awt.event.ComponentListener#componentShown(java.awt.event.ComponentEvent)
	 */
	@Override
	public void componentShown(ComponentEvent e) {
		//No op
	}

	/**
	 * Cycle.
	 */
	protected abstract void cycle();

	/**
	 * Draw board.
	 *
	 * @param g the graphics item to draw on.
	 */
	protected abstract void drawBoard(Graphics2D g);

	/**
	 * Move view port.
	 */
	protected void moveViewPort() {
		double difference;
		int tileCount;
		if (fsprite == null) {
			return;
		}
		
		if ((difference = (fsprite.x() + Tile.TILE_SIZE - viewPort.getMaxX())) > 0) {
			tileCount = (int)Math.ceil(difference / Tile.TILE_SIZE);
			viewPort.setMinX(viewPort.getMinX() + (tileCount * Tile.TILE_SIZE));
		}
		
		if ((difference = viewPort.getMinX() - fsprite.x()) > 0) {
			tileCount = (int)Math.ceil(difference / Tile.TILE_SIZE);
			viewPort.setMinX(viewPort.getMinX() - (tileCount * Tile.TILE_SIZE));
		}
		
		if ((difference = (fsprite.y() + Tile.TILE_SIZE - viewPort.getMaxY())) > 0) {
			tileCount = (int)Math.ceil(difference / Tile.TILE_SIZE);
			viewPort.setMinY(viewPort.getMinY() + (tileCount * Tile.TILE_SIZE));
		}
		
		if ((difference = viewPort.getMinY() - fsprite.y()) > 0) {
			tileCount = (int)Math.ceil(difference / Tile.TILE_SIZE);
			viewPort.setMinY(viewPort.getMinY() - (tileCount * Tile.TILE_SIZE));
		}
	}
	
	/**
	 * New map.
	 *
	 * @param x the x
	 * @param y the y
	 */
	protected void newMap(int x, int y) {
		tiles = new TileMap(x, y);
		boardBounds = new Bounds(0, 0, x * Tile.TILE_SIZE, y * Tile.TILE_SIZE);
	}
	
	/* (non-Javadoc)
	 * @see javax.swing.JComponent#paint(java.awt.Graphics)
	 */
	@Override
	public void paint(Graphics g) {
		super.paint(g);
		synchronized(animationLock) {
			drawBoard((Graphics2D)g);
		}
		Toolkit.getDefaultToolkit().sync();
		g.dispose();
	}
	
	/* (non-Javadoc)
	 * @see java.lang.Runnable#run()
	 */
	@Override
	public void run() {
		long timeDiff, sleep;
		
		while (true) {
			drawStartTime = System.currentTimeMillis();
			
			cycle();
			repaint();
			
			timeDiff = System.currentTimeMillis() - drawStartTime;
			sleep = Math.max(DELAY - timeDiff, 2L);
			
			try {
				Thread.sleep(sleep);
			}
			catch (InterruptedException e) {
				animator.shutdown();
			}
		}
	}
	
}
